﻿//
// © Algobox Ltd - Benn Eichhorn
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Algobox.Objects.Finance.Orders;
using Algobox.Objects.Finance.Assets.Stock;
using System.Threading;

namespace Algobox.Structure.Finance.Strategies.SmartStoploss
{
    public class SmartStoplossOrder : LimitSnatcher.ISnatched, Algobox.Feeds.Finance.Trading.IOrderEvent, MarketFiller.IMarketFillerCallback
    {
        #region Constructors

        public SmartStoplossOrder(SmartStoplossStock manager, Strategy strategy, string account, bool isBuy, double priceTarget, double priceStop, uint quantity)
        {
            _orderId = strategy.Id;

            if (Log.IsInfoEnabled)
                Log.Info("{0}|Creating {6} stoploss for {1} in {2} with a target of {3}@{4} with a stop at {5}", _orderId, account, manager.Stock, quantity, priceTarget, priceStop, isBuy ? "buy" : "sell");

            _strategy = strategy;
            _stopOrderNow = false;
            _manager = manager;
            _isBuy = isBuy;
            _priceTarget = priceTarget;
            _priceStop = priceStop;
            _quantityRequired = quantity;
            _quantityRemaining = quantity;
            _account = account;

            _listings = new Queue<StockListing>(manager.Stock.Security.Listings.Values.OrderBy(a => a.Priority));
            _snatcher = new LimitSnatcher.LimitSnatchOrder(manager.Manager.TradingFeed, this);
            StartSnatch();
        }

        #endregion
        //////////////////
        #region Member Variables
        
        private readonly string _orderId;

        /// <summary>
        /// Logger used by this class
        /// </summary>        
        private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();

        public const int MAX_REJECT_RETRIES = 3;

        Strategy _strategy;
        SmartStoplossStock _manager;
        readonly bool _isBuy;
        double _priceTarget;
        double _priceStop;
        readonly uint _quantityRequired;
        uint _quantityRemaining;
        double _averagePrice;

        string _account;
        Order _order;

        bool _stopOrderNow;

        LimitSnatcher.LimitSnatchOrder _snatcher;
        MarketFiller.MarketFillOrder _filler;
        Queue<StockListing> _listings;

        int _rejectCount = 0;

        #endregion
        //////////////////
        #region Properties

        public string Id { get { return this.GetType().Name + "|" + _orderId; } }

        public bool IsProfitActive { get { return _order != null; } }

        public bool IsBuy { get { return _isBuy; } }

        public uint QuantityRequired { get { return _quantityRequired; } }
        public uint QuantityRemaining { get { return _quantityRemaining; } }

        public double AveragePrice { get { return _averagePrice; } }

        public double PriceTarget
        {
            get { return _priceTarget; }
            set
            {
                lock (_strategy)
                {
                    _priceTarget = value;
                    // change order here
                    if (_order != null)
                    {
                        Log.Info("{0}|PriceTarget changed to {3} so updating Order({1}) Message({2})", _orderId, _order.ToString(), value);
                        string message;
                        if (!_manager.Manager.TradingFeed.TryReplaceOrder(_order, out message, newPrice: _order.Stock.TickSize.Round(_priceTarget)))
                            Log.Error("{0}|PriceTarget changed but failed to replace Order({1}) Message({2})", _orderId, _order.ToString(), message);
                    }
                }
            }
        }

        public double PriceStop
        {
            get { return _priceStop; }
            set
            {
                lock (_strategy)
                {
                    Log.Info("{0}|PriceStop changed to {1} so checking order", _orderId, value);
                    _priceStop = value;
                    // check stop against market    
                    StartSmart();
                }
            }
        }

        #endregion
        //////////////////
        #region Functions

        private void StartSnatch()
        {
            lock (_strategy)
            {
                if (_listings.Count == 0)
                {
                    StartSmart();
                    return;
                }

                StockListing stock;
                string message;
                while (_listings.Count > 0)
                {
                    stock = _listings.Dequeue();

                    if (Log.IsInfoEnabled)
                        Log.Info("{0}|StartSnatch Attempting to snatch {1} on ", _orderId, _quantityRemaining, stock.ToString());

                    if (_snatcher.Snatch(_account, stock, _isBuy, stock.TickSize.Round(_priceTarget), _quantityRemaining, out message))
                        break;
                    else
                        Log.Warn("{2}|StartSnatch Failed to snatch for {0} with Message({1})", _manager, message, _orderId);

                    if (_listings.Count == 0)
                        StartSmart();
                }
            }
        }

        private void StartSmart()
        {
            lock (_strategy)
            {
                if (_stopOrderNow)
                {
                    _stopOrderNow = false;
                    if (_order != null)
                    {
                        if (Log.IsInfoEnabled)
                            Log.Info("{0}|StartSmart Stop out order at market, pulling profit order", _orderId);

                        string message;
                        if (!_manager.Manager.TradingFeed.TryCancelOrder(_order, out message))
                            Log.Error("{0}|StartSmart Failed to pull order! Order({1})", _orderId, _order.ToString());
                    }
                    else if (_filler == null)
                    {
                        if (Log.IsInfoEnabled)
                            Log.Info("{0}|StartSmart Stop out order at market", _orderId);
                        _filler = new MarketFiller.MarketFillOrder(_manager.Manager.TradingFeed, this, _manager.Stock.Security, _account, _isBuy, _priceStop, _quantityRemaining, Objects.Finance.Orders.Order.TimeInForce.IOC);
                        _filler.Start();
                    }
                    return;
                }
                // time to stop
                if (CheckStopPrice())
                {
                    if (_order != null)
                    {
                        if (Log.IsInfoEnabled)
                            Log.Info("{0}|StartSmart Stoploss price met for {1}, pulling profit order and stopping out", _orderId, _manager.Stock);

                        string message;
                        if (!_manager.Manager.TradingFeed.TryCancelOrder(_order, out message))
                            Log.Error("{0}|StartSmart Failed to pull order! Order({1})", _orderId, _order);
                    }
                    else if (_filler == null)
                    {
                        if (Log.IsInfoEnabled)
                            Log.Info("{0}|StartSmart Stoploss price met for {1}, trading out at market", _orderId, _manager.Stock);
                        _filler = new MarketFiller.MarketFillOrder(_manager.Manager.TradingFeed, this, _manager.Stock.Security, _account, _isBuy, _priceStop, _quantityRemaining, Objects.Finance.Orders.Order.TimeInForce.IOC);
                        _filler.Start();
                    }
                }
                // or stick with profit order
                else if (_order == null)
                {
                    _order = new Order(_account, _manager.Stock, _isBuy, _manager.Stock.TickSize.Round(_priceTarget), _quantityRemaining, Order.OrderType.Limit, Order.TimeInForce.DAY);
                    string message;
                    if (!_manager.Manager.TradingFeed.TrySubmitOrder(_order, this, out message))
                        Log.Fatal("{0}|StartSmart Failed to submit order! Order({1}) Message({2})", _orderId, _order.ToString(), message);
                }
            }
        }
        
        /// <summary>
        /// Check if we have breached our stop price
        /// </summary>
        /// <returns>Returns true when price has preached the stop price</returns>
        private bool CheckStopPrice()
        {
            if (_isBuy)
            {
                if (_manager.Stock.Security.Prices.Ask.Price > 0 && _manager.Stock.Security.Prices.Ask.Price >= _priceStop)
                    return true;
                else if (_manager.Stock.Security.Prices.Last.Price > 0 && _manager.Stock.Security.Prices.Last.Price >= _priceStop)
                    return true;
                else
                    return false;
            }
            else
            {
                if (_manager.Stock.Security.Prices.Bid.Price > 0 && _manager.Stock.Security.Prices.Bid.Price <= _priceStop)
                    return true;
                else if (_manager.Stock.Security.Prices.Last.Price > 0 && _manager.Stock.Security.Prices.Last.Price <= _priceStop)
                    return true;
                else
                    return false;
            }
        }
        
        /// <summary>
        /// Stops out current order at market
        /// </summary>
        public void StopOrder()
        {
            _stopOrderNow = true;
            StartSmart();
        }


        public void OnPriceLast(Objects.Finance.Prices.PriceVolume price)
        {
            if (Monitor.TryEnter(_strategy))
            {
                try
                {
                    if (price.Price > 0)
                    {
                        if ((_isBuy && price.Price >= _priceStop)
                            || (!_isBuy && price.Price <= _priceStop))
                        {
                            if (_order != null)
                            {
                                if (Log.IsInfoEnabled)
                                    Log.Info("{0}|OnPriceLast Stoploss price met for {1}, pulling profit order and stopping out", _orderId, _manager.Stock);

                                string message;
                                if (!_manager.Manager.TradingFeed.TryCancelOrder(_order, out message))
                                    Log.Error("Failed to pull order! Order({0})", _order);
                            }
                            else if (_filler == null)
                            {
                                if (Log.IsInfoEnabled)
                                    Log.Info("{0}|OnPriceLast Stoploss price met for {1}, trading out at market", _orderId, _manager.Stock);
                                _filler = new MarketFiller.MarketFillOrder(_manager.Manager.TradingFeed, this, _manager.Stock.Security, _account, _isBuy, _priceStop, _quantityRemaining, Objects.Finance.Orders.Order.TimeInForce.IOC);
                                _filler.Start();
                            }
                        }                        
                    }
                }
                finally
                {
                    Monitor.Exit(_strategy);
                }
            }
        }

        public void OnPriceAsk(Objects.Finance.Prices.PriceVolume price)
        {
            if (Monitor.TryEnter(_strategy))
            {
                try
                {
                    if (_isBuy && price.Price >= _priceStop)
                    {
                        if (_order != null)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|OnPriceAsk Stoploss price met for {1}, pulling profit order and stopping out", _orderId, _manager.Stock);

                            string message;
                            if (!_manager.Manager.TradingFeed.TryCancelOrder(_order, out message))
                                Log.Error("Failed to pull order! Order({0})", _order);
                        }
                        else if (_filler == null)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|OnPriceAsk Stoploss price met for {1}, trading out at market", _orderId, _manager.Stock);
                            _filler = new MarketFiller.MarketFillOrder(_manager.Manager.TradingFeed, this, _manager.Stock.Security, _account, _isBuy, _priceStop, _quantityRemaining, Objects.Finance.Orders.Order.TimeInForce.IOC);
                            _filler.Start();
                        }
                    }
                }
                finally
                {
                    Monitor.Exit(_strategy);
                }
            }
        }

        public void OnPriceBid(Objects.Finance.Prices.PriceVolume price)
        {
            if (Monitor.TryEnter(_strategy))
            {
                try
                {
                    if (!_isBuy && price.Price > 0 && price.Price <= _priceStop)
                    {
                        if (_order != null)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|OnPriceBid Stoploss price met for {1}, pulling profit order and stopping out", _orderId, _manager.Stock);

                            string message;
                            if (!_manager.Manager.TradingFeed.TryCancelOrder(_order, out message))
                                Log.Error("Failed to pull order! Order({0})", _order);
                        }
                        else if (_filler == null)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|OnPriceBid Stoploss price met for {1}, trading out at market", _orderId, _manager.Stock);
                            _filler = new MarketFiller.MarketFillOrder(_manager.Manager.TradingFeed, this, _manager.Stock.Security, _account, _isBuy, _priceStop, _quantityRemaining, Objects.Finance.Orders.Order.TimeInForce.IOC);
                            _filler.Start();
                        }
                    }
                }
                finally
                {
                    Monitor.Exit(_strategy);
                }
            }
        }

        public void OnPhaseChange()
        {
            if (Monitor.TryEnter(_strategy))
            {
                try
                {
                    if (_order != null)
                    {
                        if (Log.IsErrorEnabled)                   
                            Log.Error("{0}|OnPhaseChange NOT HANDLED! Phase has changed to {1}", _orderId, _manager.Stock.Phase);                     
                    }
                }
                finally
                {
                    Monitor.Exit(_strategy);
                }
            }
        }

        private void RegisterFill(Order order)
        {
            uint filled = _quantityRequired - _quantityRemaining;
            _quantityRemaining -= order.QuantityFilled;
            if (filled > 0)
                _averagePrice = ((filled * _averagePrice) + (order.QuantityFilled * (double)order.PriceAverage)) / (filled + order.QuantityFilled);
            else
                _averagePrice = (double)order.PriceAverage;
            _manager.OrderFill(this);            
        }

        #region ISnatched Members

        public void OnSnatchComplete(LimitSnatcher.LimitSnatchOrder snatcher, Order order)
        {
            lock (_strategy)
            {
                if (Log.IsInfoEnabled)
                   Log.Info("{3}|OnSnatchComplete Snatch complete, got {0}@{1} for {2}", order.QuantityFilled, order.PriceAverage, order.Stock, _orderId);
                if (order.QuantityFilled != 0)
                {
                    RegisterFill(order);
                }
                if (_quantityRemaining == 0)
                {
                    _manager.CompletedOrder(this);                    
                }
                else
                {
                    StartSnatch();
                }
            }
        }

        public void OnSnatchRejected(LimitSnatcher.LimitSnatchOrder snatcher, Order order)
        {
            lock (_strategy)
            {
                if(Log.IsWarnEnabled)
                    Log.Warn("{0}|OnSnatchRejected Snatch rejected Message({1}) Order({2})", _orderId, order.Text, order);
                StartSnatch();
            }
        }

        #endregion

        #region IMarketFillerCompleted Members

        public void OnMarketFillerCompleted(MarketFiller.MarketFillOrder caller)
        {
            lock (_strategy)
            {    
                uint filled = _quantityRequired - _quantityRemaining;
                _quantityRemaining -= caller.Quantity;
                if (filled > 0)
                    _averagePrice = ((filled * _averagePrice) + (caller.Quantity * (double)caller.AveragePrice)) / (filled + caller.Quantity);
                else
                    _averagePrice = (double)caller.AveragePrice;
                _manager.OrderFill(this);

                if (_quantityRemaining == 0 && Log.IsInfoEnabled)
                    Log.Info("{0}|OnMarketFillerCompleted Completed SmartStopLoss", _orderId);
                else if(Log.IsFatalEnabled)
                    Log.Fatal("{0}|OnMarketFillerCompleted but we still have a remain quantity outstanding of {1}?", _orderId, _quantityRemaining);

                _manager.CompletedOrder(this);
            }
        }

        public void OnMarketFillerFailed(MarketFiller.MarketFillOrder caller)
        {
            lock (_strategy)
            {
                if (caller.QuantityFilled > 0)
                {
                    if (Log.IsErrorEnabled)
                        Log.Error("{0}|OnMarketFillerFailed failed but has executed {1}", _orderId, caller.QuantityFilled);
                    
                    uint filled = _quantityRequired - _quantityRemaining;
                    _quantityRemaining -= caller.Quantity;
                    if (filled > 0)
                        _averagePrice = ((filled * _averagePrice) + (caller.Quantity * (double)caller.AveragePrice)) / (filled + caller.Quantity);
                    else
                        _averagePrice = (double)caller.AveragePrice;

                    _manager.OrderFill(this);
                }

                if (_quantityRemaining > 0)
                {
                    if(Log.IsErrorEnabled)
                        Log.Error("{0}|OnMarketFillerFailed and we still have quantity remaining so trading out the rest with an IOC Market order", _orderId, _order.ToString());
                    
                    // sending exit order at market
                    _order = new Order(_account, _manager.Stock, _isBuy, 0, _quantityRemaining, Order.OrderType.Market, Order.TimeInForce.IOC);

                    string message;
                    if (!_manager.Manager.TradingFeed.TrySubmitOrder(_order, this, out message))
                        Log.Fatal("{0}|OnMarketFillerFailed Failed to submit order! Order({1}) Message({2})", _orderId, _order.ToString(), message);
                }
                else
                {
                    if (Log.IsWarnEnabled)
                        Log.Warn("{0}|OnMarketFillerFailed failed but we have actually completed remaining quantity anyway!?!?!", _orderId);

                    _manager.CompletedOrder(this);                  
                }
            }
        }


        #endregion

        #region IOrderEvent Members

        public void OrderUpdate(Order order, Order.Status state)
        {
            if (Log.IsDebugEnabled)
                Log.Debug("{0}|OrderUpdate recieved order in state {1} Order({1})", _orderId, state, order.ToString());

            lock (_strategy)
            {
                if (order != _order)
                {
                    if (Log.IsErrorEnabled)
                        Log.Error("{0}|OrderUpdate Unknown Order({1})", _orderId, order.ToString());
                    return;
                }

                switch (state)
                {
                    // order complete
                    case Order.Status.Filled:
                    case Order.Status.Canceled:
                    case Order.Status.Stopped:
                        if (order.QuantityFilled != 0)
                        {
                            RegisterFill(order);
                        }
                        _order = null;

                        if (_quantityRemaining == 0)
                        {
                            if (Log.IsErrorEnabled)
                                Log.Error("{0}|OrderUpdate Completed SmartStopLoss Order({3})", _orderId, state, order.QuantityRemaining, order.ToString());
                            _manager.CompletedOrder(this);
                        }
                        else
                        {
                            if (Log.IsErrorEnabled)
                                Log.Error("{0}|OrderUpdate {1} QuantityRemaining({2}) Order({3})", _orderId, state, order.QuantityRemaining, order.ToString());
                            StartSmart();
                        }
                        break;

                    // deal with a reject or cancel the order of a replace reject
                    case Order.Status.Rejected:
                        if (Log.IsErrorEnabled)
                            Log.Error("{0}|OrderUpdate Order rejected! Order({1})", _orderId, order.ToString());
                        if (order.QuantityFilled != 0)
                        {
                            RegisterFill(order);
                        }
                        _order = null;

                        if (_quantityRemaining == 0)
                        {
                            _manager.CompletedOrder(this);
                        }
                        else if (++_rejectCount == MAX_REJECT_RETRIES)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|OrderUpdate Too many rejects for {1}, trading out at market Order({2})", _orderId, _manager.Stock, order.ToString());
                            _filler = new MarketFiller.MarketFillOrder(_manager.Manager.TradingFeed, this, _manager.Stock.Security, _account, _isBuy, _priceStop, _quantityRemaining, Objects.Finance.Orders.Order.TimeInForce.IOC);
                            _filler.Start();
                        }
                        else
                        {
                            StartSmart();
                        }
                        break;

                    // otherwise wait for the correct state
                    default:
                        break;
                }

            } // end lock
        }

        #endregion


        #endregion


    }
}
